//
// RS Game Framework
// Copyright © 2010 Jedd Haberstro
// jhaberstro@gmail.com
// 
// $Id:
//

#ifndef RS_GRAPHICS_TEXTUREFORMAT_HPP
#define RS_GRAPHICS_TEXTUREFORMAT_HPP

#include "rs/Assert.hpp"
#include "rs/Core.hpp"
#include "rs/Portability.hpp"

namespace rs
{
    namespace graphics
    {   
        class TextureFormat
        {
        public:
            
            // [Type][Components][Components Bit Count]
            enum Enum
            {
                Unknown = 0,
                
                // Unsigned normalized formats
                UNormRGB8 = 1,
                UNormRGBA8 = 2,
                UNormRGB16 = 3,
                UNormRGBA16 = 4,
                
                // Signed normalized formats
                SNormRGB8 = 5,
                SNormRGBA8 = 6,
                SNormRGB16 = 7,
                SNormRGBA16 = 8,
                
                // Unsigned integer formats
                UIntRGB16 = 9,
                UIntRGBA16 = 10,
                UIntRGB32 = 11,
                UIntRGBA32 = 12,
                
                // Signed integer formats
                SIntRGB16 = 13,
                SIntRGBA16 = 14,
                SIntRGB32 = 15,
                SIntRGBA32 = 16,
                
                // Float formats
                FloatRGB16 = 17,
                FloatRGBA16 = 18,
                FloatRGB32 = 19,
                FloatRGBA32 = 20,
                
                // Packed formats
                FloatR11G11B10 = 21,
                UNormR5G6R5 = 22,
                UNormR5G5R5A1 = 23,
                UNormRGBA4 = 24,
                UNormR10G10B10A2 = 25,
                
                // Depth(-Stencil) formats
                UNormDepth16 = 26,
                UNormDepth24 = 27,
                UNormDepth24Stencil8 = 28,
                FloatDepth32 = 29,
                
                // Compressed formats
                DXT1 = 30,
                DXT3 = 31,
                DXT5 = 32
            };
            
        public:
            
            TextureFormat();
            
            TextureFormat(TextureFormat::Enum format);
            
            TextureFormat::Enum GetFormat() const;
            
            Boolean IsPlain() const;
            
            Boolean IsPacked() const;
            
            Boolean IsDepth() const;
            
            Boolean IsStencil() const;
            
            Boolean IsCompressed() const;
            
            Boolean IsUnsignedNormalized() const;
            
            Boolean IsSignedNormalized() const;
            
            Boolean IsUnsignedInteger() const;
            
            Boolean IsSignedInteger() const;
            
            Boolean IsFloat() const;
            
            UInt32 GetChannelCount() const;
            
            UInt32 GetBytesPerChannel() const;
            
            UInt32 GetBytesPerPixel() const;
            
            UInt32 GetSize(UInt32 width, UInt32 height) const;
                        
        private:
            
            TextureFormat::Enum format_;
            UInt32 channelCount_;
            UInt32 bytesPerChannel_;
            UInt32 bytesPerPixel_;
        };
        
        
        inline Boolean operator==(TextureFormat const& lhs, TextureFormat const& rhs) {
            return lhs.GetFormat() == rhs.GetFormat();
        } 
        
        inline Boolean operator!=(TextureFormat const& lhs, TextureFormat const& rhs) {
            return lhs.GetFormat() != rhs.GetFormat();
        }
        
        inline Boolean operator==(TextureFormat const& lhs, TextureFormat::Enum const rhs) {
            return lhs.GetFormat() == rhs;
        } 
        
        inline Boolean operator!=(TextureFormat const& lhs, TextureFormat::Enum const rhs) {
            return lhs.GetFormat() != rhs;
        }
        
        inline Boolean operator==(TextureFormat::Enum const lhs, TextureFormat const& rhs) {
            return lhs == rhs.GetFormat();
        } 
        
        inline Boolean operator!=(TextureFormat::Enum const lhs, TextureFormat const& rhs) {
            return lhs != rhs.GetFormat();
        }
        
        inline TextureFormat::TextureFormat()
        : format_(Unknown) {
        }
        
        inline TextureFormat::TextureFormat(TextureFormat::Enum format)
        : format_(format) {
            static UInt32 const channels[] = {
                0,
                3, 4, 3, 4,
                3, 4, 3, 4,
                3, 4, 3, 4,
                3, 4, 3, 4,
                3, 4, 3, 4,
                3, 3, 4, 4, 4,
                1, 1, 2, 1,
                3, 4, 4
            };
            
            static UInt32 const bpc[] = {
                0,
                1, 1, 2, 2,
                1, 1, 2, 2,
                2, 2, 4, 4,
                2, 2, 4, 4,
                2, 2, 4, 4,               
            };
            
            static UInt32 const bpp[] = {
				0,
                3, 4, 6, 8,
                3, 4, 6, 8,
                6, 8, 12, 16,
                6, 8, 12, 16,
                6, 8, 12, 16,
                4, 2, 2, 2, 4,
                2, 3, 4, 4
            };
            
            channelCount_ = channels[format_];
            BranchlessTernary(!(IsPacked() || IsDepth() || IsCompressed()), bpc[format_], 0U, bytesPerChannel_);
            BranchlessTernary(!IsCompressed(), bpp[format_], 0U, bytesPerPixel_);
        }
        
        inline TextureFormat::Enum TextureFormat::GetFormat() const {
            return format_;
        }
        
        inline Boolean TextureFormat::IsPlain() const {
            return format_ <= SIntRGBA32;
        }
        
        inline Boolean TextureFormat::IsPacked() const {
            return FloatR11G11B10 <= format_ && format_ <= UNormR10G10B10A2;
        }
        
        inline Boolean TextureFormat::IsDepth() const {
            return UNormDepth16 <= format_ && format_ <= FloatDepth32;
        }
        
        inline Boolean TextureFormat::IsStencil() const {
            return format_ == UNormDepth24Stencil8;
        }
        
        inline Boolean TextureFormat::IsCompressed() const {
            return format_ >= DXT1;
        }
        
        inline Boolean TextureFormat::IsUnsignedNormalized() const {
            return UNormRGB8 <= format_ && format_ <= UNormRGBA16;
        }
        
        inline Boolean TextureFormat::IsSignedNormalized() const {
            return SNormRGB8 <= format_ && format_ <= SNormRGBA16;
        }
        
        inline Boolean TextureFormat::IsUnsignedInteger() const {
            return UIntRGB16 <= format_ && format_ <= UIntRGBA32;
        }
        
        inline Boolean TextureFormat::IsSignedInteger() const {
            return UIntRGB16 <= format_ && format_ <= UIntRGBA32;
        }
        
        inline Boolean TextureFormat::IsFloat() const {
            return FloatRGB16 <= format_ && format_ <= FloatRGBA32;
        }
        
        inline UInt32 TextureFormat::GetChannelCount() const {
            return channelCount_;
        }
        
        inline UInt32 TextureFormat::GetBytesPerChannel() const {
            RS_ASSERT(!IsPacked(), "Unable to retrieve bytes per channel for packed format");
            RS_ASSERT(!IsDepth(), "Unable to retrieve bytes per channel for depth format");
            RS_ASSERT(!IsCompressed(), "Unable to retrieve bytes per channel for compressed format");
            return bytesPerChannel_;
        }
        
        inline UInt32 TextureFormat::GetBytesPerPixel() const {
            return bytesPerPixel_;
        }
        
        inline UInt32 TextureFormat::GetSize(UInt32 width, UInt32 height) const {
            if (!IsCompressed()) {
                return width * height * GetBytesPerPixel();
            }
            else if (format_ == DXT1) {
                return ((width + 3) >> 2) * ((height + 3) >> 2) * 8;
            }
            else if (format_ == DXT3 || format_ == DXT5) {
                return ((width + 3) >> 2) * ((height + 3) >> 2) * 16; 
            }
            
            return 0;
        }
    }
}

#endif // RS_GRAPHICS_TEXTUREFORMAT_HPP